package claim.geoobj;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import claim.geom.Feature;
import claim.geom.GeometryException;
import claim.geom.PolygonFeature;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.operation.buffer.BufferOp;
import com.vividsolutions.jts.operation.buffer.BufferParameters;

/**
 * A (complete) buffer for a topographical object.
 * 
 * @author <a href="mailto:christoph.kinkeldey@hcu-hamburg.de">Christoph
 *         Kinkeldey</a>
 * 
 */
public class Buffer {

	private static final Log LOG = LogFactory.getLog(Class.class.getName());

	private String id = "";
	private Polygon bufferPolygon;
	/** Boundary on object's side */
	private LinearRing objBoundary;
	/** Boundary on opposite side of object */
	private LinearRing neighBoundary;

	private double bufferSize;

	public static final double DEFAULT_BUFFER_SIZE = 1e-5;

	public Buffer(Boundary boundary) throws GeometryException {
		this.bufferSize = DEFAULT_BUFFER_SIZE;
		if (boundary == null) {
			throw new GeometryException("boundary must not be null.");
		} else {
			this.id = boundary.getObjId();
			List<Geometry> bufferGeometries = new ArrayList<Geometry>();
			for (String neighId : boundary.getNeighbourIDs()) {
				Geometry boundGeom = boundary.getBoundary(neighId);
				bufferSize = boundary.getBufferSize(neighId);
				Geometry buffer = createBufferGeometry(boundGeom, bufferSize);
				if (!buffer.isEmpty()) {
					bufferGeometries.add(buffer);
				}
			}
			Geometry[] geometries = bufferGeometries.toArray(new Geometry[0]);
			GeometryCollection collection = new GeometryFactory().createGeometryCollection(geometries);
			if (collection.isEmpty()) {
				throw new GeometryException("geometry is empty!");
			} else {
				Geometry union = collection.union();
				if (union instanceof MultiPolygon) {
					this.bufferPolygon = (Polygon) union.getGeometryN(0);
				} else {
					this.bufferPolygon = (Polygon) union;
				}

				if (bufferPolygon.getExteriorRing() == null) {
					throw new GeometryException("incomplete buffer: exterior: "
							+ (bufferPolygon.getExteriorRing() != null)
							+ " inner: " + bufferPolygon.getNumInteriorRing());
				} else if (bufferPolygon.getNumInteriorRing() == 0) {
					//throw new GeometryException("could not create buffer of size "+bufferSize);
					LOG.debug("degenerated buffer: no inner ring! adding articifial inner ring");
					Point centroid = bufferPolygon.getBoundary().getCentroid();
					Coordinate[] coords = new Coordinate[5];
					double x = centroid.getX();
					double y = centroid.getY();
					coords[0] = new Coordinate(x-DEFAULT_BUFFER_SIZE, y-DEFAULT_BUFFER_SIZE);
					coords[1] = new Coordinate(x+DEFAULT_BUFFER_SIZE, y-DEFAULT_BUFFER_SIZE);
					coords[2] = new Coordinate(x+DEFAULT_BUFFER_SIZE, y+DEFAULT_BUFFER_SIZE);
					coords[3] = new Coordinate(x-DEFAULT_BUFFER_SIZE, y+DEFAULT_BUFFER_SIZE);
					coords[4] = new Coordinate(x-DEFAULT_BUFFER_SIZE, y-DEFAULT_BUFFER_SIZE);
					LinearRing[] holes = new LinearRing[] {new GeometryFactory().createLinearRing(coords)};
					LinearRing shell = (LinearRing) bufferPolygon.getExteriorRing();
					Polygon buffer = new GeometryFactory().createPolygon(shell, holes);
					this.neighBoundary = shell;
					this.objBoundary = holes[0];
					this.bufferPolygon = buffer;
				} else {
					if (boundary.isOuter()) {
						this.neighBoundary = (LinearRing) bufferPolygon
								.getExteriorRing();
						this.objBoundary = (LinearRing) bufferPolygon
								.getInteriorRingN(0);
					} else {
						this.neighBoundary = (LinearRing) bufferPolygon
								.getInteriorRingN(0);
						this.objBoundary = (LinearRing) bufferPolygon
								.getExteriorRing();
					}
				}
			}
		}

	}
	
	
	
	public String getId() {
		return id;
	}
	
	public double getSize() {
		return bufferSize;
	}
	

	public LinearRing getObjBoundary() {
		return objBoundary;
	}

	public LinearRing getNeighBoundary() {
		return neighBoundary;
	}

	private Geometry createBufferGeometry(Geometry geometry, double bufferSize) {
		Geometry[] geometries = new Geometry[geometry.getNumGeometries()];
		for (int i = 0; i < geometry.getNumGeometries(); i++) {
			Coordinate[] coords = geometry.getGeometryN(i).getCoordinates();
			if (coords.length > 1) {
//				geometries[i] = geometry.buffer(bufferSize, 4);
				geometries[i] = geometry.buffer(bufferSize, 2, BufferParameters.CAP_SQUARE);
			} else if (coords.length == 1) {
				Point point = new GeometryFactory().createPoint(coords[0]);
				geometries[i] = point.buffer(bufferSize);
			}
		}
		return new GeometryCollection(geometries, new GeometryFactory());
	}

	public Geometry getGeometry() {
		return this.bufferPolygon;
	}

	public Feature getAsFeature() {
		Feature feature = new PolygonFeature(id, "", this.bufferPolygon);
		return feature;
	}

}
